package xyz.lwm.lazycat.utility.scan;

import xyz.lwm.lazycat.utility.StringUtil;

import java.util.ArrayList;
import java.util.List;

import static xyz.lwm.lazycat.utility.scan.Scanner.END;
import static xyz.lwm.lazycat.utility.scan.Scanner.SLASH;
import static xyz.lwm.lazycat.utility.scan.Scanner.START;

/**
 * 路径正则式
 *
 * @author lwm
 */
public final class PathRegex {

    // 所有路径正则式 '/**/'
    public static final String REG_PATHS = "/\\*\\*/";

    // 匹配所有字符, 除了 \n 和 \r
    public static final String REG_WILDCARD = ".*";

    // 匹配常用字符, 除了 \n , \r 和 \/ ，如此使用是为了确定只有 /**/ 才能匹配所有文件夹路径
    public static final String REG_CHAR = "[^\\n\\r\\/]";

    // 正则表达式, 用于匹配 /
    public static final String REG_SLASH = "\\/";

    // 正则表达式, 用于匹配 /
    public static final String REG_SLASH_END = "\\/$";

    // 正则表达式, 用于匹配文件路径
    private final String regex;

    public PathRegex(String regex) {
        this.regex = regex;
    }

    /**
     * 将通配符转换为正则式 <br>
     * * 匹配任意多个字符, 但 \n \r / 这三种字符除外; <br>
     * ? 匹配任意单个字符, 但 \n \r / 这三种字符除外; <br>
     * ** 匹配任意多个文件夹, 包括文件. <br>
     *
     * @param pattern pattern
     * @return regex
     */
    // a/b/c
    // a/b/c/*.txt
    // **/a/**
    // a/**/b/app-*.yml
    public static PathRegex toRegex(String pattern) {
        return new PathRegex(resolveRegex(pattern));
    }

    // 将通配符转换为正则式
    // 通配符 * 可以匹配任意字符, 但 / 除外, 因为这样无法限定文件夹,
    // 如果想通配文件夹, 需要使用 /**/
    private static String resolveRegex(String pattern) {
        // adapt to start with `/`
        String head = "";
        if (pattern.startsWith("**/")) {
            head = REG_WILDCARD + REG_SLASH;
            pattern = pattern.substring(3);
        } else if (pattern.startsWith("/**/")) {
            head = REG_WILDCARD + REG_SLASH;
            pattern = pattern.substring(4);
        }
        // adapt to end with `/`
        String tail = "";
        if (pattern.endsWith("/**")) {
            tail = REG_SLASH + REG_WILDCARD;
            pattern = pattern.substring(0, pattern.length() - 3);
        } else if (pattern.endsWith("/**/")) {
            tail = REG_SLASH + REG_WILDCARD;
            pattern = pattern.substring(0, pattern.length() - 4);
        }

        String[] arr = pattern.split(REG_PATHS, 10);
        List<StringBuilder> regs = new ArrayList<>();
        for (String a : arr) {
            StringBuilder reg = new StringBuilder();
            regs.add(reg);

            if (StringUtil.isNotEmpty(a)) {
                // convert char to regex
                for (char c : a.toCharArray()) {
                    switch (c) {
                        case Scanner.WILDCARD:
                            reg.append(REG_CHAR).append(c);
                            break;
                        case Scanner.CHAR:
                            reg.append(REG_CHAR);
                            break;
                        case SLASH:
                        case  Scanner.DOT:
                            reg.append("\\").append(c);
                            break;
                        default: reg.append(c);
                    }
                }
            }
        }

        // concat regexes
        StringBuilder regex = new StringBuilder().append(START);
        regex.append(head);

        for (int i = 0; i < regs.size(); i++) {
            // so we need to add `.*` to match the middle folders
            if (i > 0) {
                // 左侧需要添加 `/`， 用于精确范围, 少一个 `/`，可能导致匹配不准确
                // 右侧不添加 `/`，因为要匹配当前文件夹的文件以及子文件夹的文件
                regex.append(REG_SLASH).append(REG_WILDCARD);
            }
            regex.append(regs.get(i));
        }

        regex.append(tail);
        return regex.append(END).toString();
    }

    // 添加 `/` 来匹配中间的文件夹, 动态添加, `/` 不重复
    private static void adaptSlash( StringBuilder regex) {
        if (regex.length() < 2 || !REG_SLASH.equals(regex.substring(regex.length() - 2))) {
            regex.append(REG_SLASH);
        }
    }

    public boolean matches(String str) {
        return str != null && str.matches(regex);
    }

    @Override
    public String toString() {
        return regex;
    }

}
